﻿using ServerListerner.Attributes;
using ServerListerner.Enums;
using ServerListerner.Helper;
using ServerListerner.Model;

using System;
using System.Collections.Generic;
using System.Data;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Net.WebSockets;

namespace ServerListerner.Handlers
{
    internal class RouterHandler : IRouterHandler
    {
        private List<Route> routes = new List<Route>();
        public List<Route> Routes { get => routes; }


        public RouterHandler()
        {
            InitRoutes();
        }
        private void InitRoutes()
        {
            Assembly[] assembly = AppDomain.CurrentDomain.GetAssemblies();
            assembly = assembly.Where(s => !s.FullName.StartsWith(nameof(System))
            && !s.FullName.StartsWith(nameof(Microsoft)) && !s.FullName.ToLower().StartsWith("netstandard")
            ).ToArray();
            foreach (Assembly asm in assembly)
            {
                Type[] types = asm.GetTypes();
                foreach (var type in types)
                {
                    foreach (var method in type.GetMethods())
                    {
                        var routeAttribute = method.GetCustomAttribute<RouterAttribute>();
                        if (routeAttribute != null)
                        {
                            if (!routes.Any(s => s.Path == routeAttribute.Path && s.RequestMethod == routeAttribute.Method.GetDescription()?.ToLower()))
                                routes.Add(new Route()
                                {
                                    ClassName = type.Name,
                                    NameSpace = type.Namespace,
                                    Method = method,
                                    Path = routeAttribute.Path,
                                    RequestMethod = routeAttribute.Method.GetDescription()?.ToLower()
                                });
                        }
                    }
                }
            }
        }
        public object? HandleRequest(Route? _route, HTTPRequest request)
        {
            if (_route == null || _route.Method == null || _route.Method.DeclaringType.BaseType != typeof(HTTPRequest))
                return null;
            object instance = Activator.CreateInstance(_route.Method.DeclaringType);
            Type baseType = _route.Method.DeclaringType.BaseType;
            PropertyInfo[] properties = baseType.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                if (property.CanWrite)
                    property.SetValue(instance, property.GetValue(request));
            }
            var methodParameters = _route.Method.GetParameters();
            if (methodParameters == null || methodParameters.Length == 0)
                return ReturnObject(_route.Method,instance, null);
            if (request.Params?.Count > 0)
                return ParamsAction(_route, instance, request.Params);
            var contenttype = request.Headers?.Where(s => s.Key.ToLower() == "content-type").FirstOrDefault().Value;
            string[]? _str_Context = contenttype?.Split(new char[] { ',', ';' });
            string type = _str_Context[0]?.Split('/')[1] ?? "";
            return type.ToLower() switch
            {
                "json" => ParamsJson(_route, instance, request.Content ?? string.Empty),
                "form-data" => ParamsFormData(_route, instance, _str_Context[1].Split('=')[1], request.Content ?? string.Empty),
                "formdata" => ParamsFormData(_route, instance, _str_Context[1].Split('=')[1], request.Content ?? string.Empty),
                _ => ReturnObject(_route.Method,instance, new object[methodParameters.Length])
            };
        }
        private object? ParamsAction(Route? _route, object instance, Dictionary<string, object> Params)
        {
            var methodParameters = _route.Method?.GetParameters();
            List<object> args = new List<object>();
            foreach (var param in methodParameters)
            {
                var data = Params.Where(s => s.Key.ToLower() == param.Name.ToLower()).FirstOrDefault().Value;
                if (param.ParameterType == typeof(Guid))
                {
                    if (Guid.TryParse(data?.ToString(), out Guid guid))
                        args.Add(guid);
                    else
                        args.Add(Guid.Empty);
                }
                else
                    args.Add(data==null?null:Convert.ChangeType(data, param.ParameterType));
            }
            return ReturnObject(_route.Method,instance, args.ToArray());
        }
        private object? ParamsJson(Route? _route, object instance, string Params)
        {
            var methodParameters = _route?.Method?.GetParameters();
            List<object?> args = new List<object?>();
            if (methodParameters==null||methodParameters.Length<=0) return ReturnObject(_route.Method, instance, args.ToArray());
            Dictionary<string, object?> valuePairs = new Dictionary<string, object?>();
            if (Params.IsJson())
                valuePairs = Params.JsonToObject<Dictionary<string, object?>>() ?? new Dictionary<string, object?>();
            else
                valuePairs.Add(methodParameters[0].Name, Params);
            foreach (var item in methodParameters)
            {
                var _param = valuePairs.Where(s => s.Key.ToLower() == item.Name.ToLower()).FirstOrDefault().Value;
                if (_param == null)
                    args.Add((Params??string.Empty).StrToObjectParam(item));
                else
                    args.Add(_param.ObjectToObjectParam(item));
            }
            return ReturnObject(_route?.Method, instance, args.ToArray());
        }
        private object? ParamsFormData(Route? _route, object instance, string sliptLine, string Params)
        {
            var methodParameters = _route.Method?.GetParameters();
            List<object?> args = new List<object?>();
            if (methodParameters == null || methodParameters.Length <= 0) return ReturnObject(_route.Method, instance, args.ToArray());
            if (Params == null || string.IsNullOrEmpty(Params))
            {
                foreach (var param in methodParameters)
                    args.Add(param.ParameterType.DefaultValue());
                return ReturnObject(_route.Method, instance, args.ToArray());
            }
            Dictionary<string, object> formdata = Params.FormDataToDictionary(sliptLine);
            foreach (var item in methodParameters)
            {
                var _param = formdata.Where(s => s.Key.ToLower() == item.Name.ToLower()).FirstOrDefault().Value;
                if (_param == null)
                    args.Add(item.ParameterType.DefaultValue());
                else
                    args.Add(_param.ObjectToObjectParam(item));
            }
            return ReturnObject(_route.Method,instance, args.ToArray());
        }

        private object? EntityParams(Type type, object? obj)
        {
            object instance = Activator.CreateInstance(type);
            if (obj == null) return instance;
            PropertyInfo[] properties = type.GetProperties();
            Type _obj = obj.GetType();
            PropertyInfo[] _obj_data = _obj.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                object value = null;
                var objval = _obj.GetProperty(property.Name);
                var val = objval.GetValue(obj);
                if (val != null)
                {
                    if (!property.PropertyType.IsGenericType)
                        value = Convert.ChangeType(val, property.PropertyType);
                    {
                        Type genericTypeDefinition = property.PropertyType.GetGenericTypeDefinition();
                        if (genericTypeDefinition == typeof(Nullable<>))
                        {
                            value = Convert.ChangeType(val, Nullable.GetUnderlyingType(property.PropertyType));
                        }
                        else
                        {
                            value = Convert.ChangeType(val, property.PropertyType);
                        }
                    }
                    property.SetValue(instance, value, null);

                }
            }
            return instance;
        }
        private object? JsonObjectParams(Type type, object? obj) => obj.ObjectToJson().JsonToObject(type);

        private object? ReturnObject(MethodInfo? Method, object instance, object[] param)
        {
            try
            {
                if(Method==null) return null;
                if (Method.ReturnType.IsGenericType && Method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
                {
                    
                    var task = (Task)Method.Invoke(instance, param);
                    task.Wait();
                    var resultProperty = task.GetType().GetProperty("Result");
                    return resultProperty?.GetValue(task);
                }
                else
                    return Method?.Invoke(instance, param);
            }
            catch (Exception ex)
            {
                return null;
            }
        }
        private object? DictionaryToObject(Dictionary<string, object> dic, Type _type)
        {
            object instance = Activator.CreateInstance(_type);
            Type type = instance.GetType();
            var propertories = type.GetProperties();
            foreach (var item in propertories)
            {
                try
                {
                    var tmp = dic.Where(s => s.Key.ToLower() == item.Name.ToLower()).FirstOrDefault();
                    if (tmp.Value != null)
                    {
                        if (item.PropertyType.IsCustomClass())
                            item.SetValue(instance, tmp.Value.CopyObjectByType(item.PropertyType));
                        else
                        {
                            string _str = tmp.Value.ToString();
                            if (item.PropertyType == typeof(Guid))
                                item.SetValue(instance, Guid.TryParse(_str, out Guid ret) ? ret : Guid.Empty);
                            else
                                item.SetValue(instance, Convert.ChangeType(_str, item.PropertyType));
                        }
                    }
                    else
                    {
                        item.SetValue(instance, null);
                    }
                }
                catch (Exception ex)
                {
                }
            }
            return instance;

        }
    }
    public class CustomObject : DynamicObject
    {
        private Dictionary<string, object> properties = new Dictionary<string, object>();

        public void SetPropertry(string key, object? value)
        {
            properties[key] = value;
        }
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            properties[binder.Name] = value;
            return true;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            return properties.TryGetValue(binder.Name, out result);
        }
    }

    public class Route
    {
        public string? Path { get; set; }
        public string? RequestMethod { get; set; }
        public MethodInfo? Method { get; set; }
        public string? NameSpace { get; set; }
        public string? ClassName { get; set; }
    }
}
